﻿namespace Hopfield.Simulator
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Data;
    using System.Globalization;
    using System.Text;
    using System.Threading;
    using System.Windows.Forms;

    [HopfieldSimulatorPage("InteliGUI")]
    public partial class HopfieldInteliGUI : UserControl, IHopfieldSimulatorPage
    {
        #region Fields

        private static object Mutex = new object();
        private readonly ToolStripStatusLabel info;
        private readonly ToolStripStatusLabel infoMSE;
        private readonly Form parentForm;
        private int pN1 = 22;
        private int pN2 = 10;
        private int pDeep = 5;
        private DateTime inteliGUILastClickTime = DateTime.Now;
        private List<int> inteliGUISequence = new List<int>();
        private System.Windows.Forms.Timer inteliGUITimer = new System.Windows.Forms.Timer();

        #endregion

        #region Constructor

        public HopfieldInteliGUI(Hopfield.Math.Hopfield hopfield,
                          ToolStripStatusLabel info,
                          ToolStripStatusLabel infoMSE,
                          Form parentForm)
        {
            InitializeComponent();

            // Dependency injection from parent.
            this.Hopfield = hopfield;
            this.info = info;
            this.infoMSE = infoMSE;
            this.parentForm = parentForm;

            // Set d
            int d = 128;
            this.Hopfield.Setd(d);
            this.textBoxD.Text = d.ToString(CultureInfo.InvariantCulture);

            // Set eta
            this.Hopfield.Seteta(0.7f);
            this.textBoxEta.Text = (0.7f).ToString(CultureInfo.InvariantCulture);

            // Set MSE
            this.Hopfield.SetMSEmin(0.3f);

            // N1, N2
            this.textBoxN1.Text = this.pN1.ToString(CultureInfo.InvariantCulture);
            this.textBoxN2.Text = this.pN2.ToString(CultureInfo.InvariantCulture);

            // Delta
            this.textBoxDeep.Text = this.pDeep.ToString(CultureInfo.InvariantCulture);

            this.panelInteliGUI.Layout += new LayoutEventHandler(panelInteliGUI_Layout);
            this.dataGridView.AutoGenerateColumns = true;
            this.inteliGUITimer.Interval = 2000;
            this.inteliGUITimer.Tick += (sender, e) =>
            {
                AddSequence(-1);
            };            
        }

        #endregion

        #region Finalizer

        ~HopfieldInteliGUI()
        {
        }

        #endregion

        #region Properties

        Hopfield.Math.Hopfield hopfield = null;

        public Hopfield.Math.Hopfield Hopfield
        {
            get { return this.hopfield; }
            private set { this.hopfield = value; }
        }

        #endregion

        #region Methods

        public void Activate()
        {
            int d = 0;
            if (true == int.TryParse(textBoxD.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out d))
            {
                this.Hopfield.Setd(d);

                if (this.ChangedD != null)
                {
                    this.ChangedD(this, EventArgs.Empty);
                }
            }

            float eta = 0.7f;
            if (true == float.TryParse(textBoxEta.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out eta))
            {
                this.Hopfield.Seteta(eta);

                if (this.ChangedEta != null)
                {
                    this.ChangedEta(this, EventArgs.Empty);
                }
            }

            int n = 0;
            if (true == int.TryParse(textBoxN1.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out n))
            {
                this.pN1 = n;
            }

            if (true == int.TryParse(textBoxN2.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out n))
            {
                this.pN2 = n;
            }

            int deep = 0;
            if (true == int.TryParse(textBoxDeep.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out deep))
            {
                this.pDeep = deep;
            }

            this.Hopfield.Recreate(this.pN1 * this.pN2);

            this.InitInteliGUI();            
        }

        private void panelInteliGUI_Layout(object sender, LayoutEventArgs e)
        {
            int nextTop = 0, nextLeft = 0;
            int maxHeight = 0, maxWidth = 0;
            int parentWidth;
            if (this.Parent != null)
            {
                parentWidth = panelInteliGUI.Parent.Width;
            }
            else
            {
                parentWidth = panelInteliGUI.Width;
            }
            foreach (Control control in panelInteliGUI.Controls)
            {
                control.Top = nextTop;
                control.Left = nextLeft;
                if (control.Height > maxHeight)
                {
                    maxHeight = control.Height;
                }
                if (control.Width > maxWidth)
                {
                    maxWidth = control.Width;
                }
                if ((nextLeft + control.Width + maxWidth) >= parentWidth)
                {
                    nextTop += maxHeight;
                    nextLeft = 0;
                }
                else
                {
                    nextLeft += control.Width;
                }
            }
            panelInteliGUI.AutoScrollPosition = new System.Drawing.Point(0, 0);
        }

        private void ReCreateInteliGUIButtons(int numberButtons)
        {
            panelInteliGUI.Controls.Clear();

            for (int i = 0; i < numberButtons; i++)
            {
                Button btn = new Button()
                {
                    Name = "buttonInteliGUI" + i.ToString(CultureInfo.InvariantCulture),
                    Text = i.ToString(CultureInfo.InvariantCulture),
                    Height = 23,
                    Width = 40
                };

                btn.Click += (sender, e) =>
                {
                    int buttonNumber = 0;
                    if (false == int.TryParse(btn.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out buttonNumber))
                    {
                        return;
                    }

                    this.AddSequence(buttonNumber);
                };

                panelInteliGUI.Controls.Add(btn);
            }
        }

        private float[] FactorySequenceAttractor()
        {            
            float[] returnAttractor = new float[this.pN1 * this.pN2];
            
            for (int i = 0; i < this.pN1; i++)
            {
                int sequenceNumber = i < inteliGUISequence.Count ? inteliGUISequence[i] : 0;
                for (int j = 0; j < this.pN2; j++)
                {
                    returnAttractor[i * this.pN2 + j] = this.Hopfield.GetFValue(sequenceNumber);
                }
            }

            return returnAttractor;
        }

        private void AddSequence(int buttonNumber)
        {
            if (buttonNumber >= 0 && this.inteliGUISequence.Count < this.pN1)
            {
                this.inteliGUISequence.Add(buttonNumber);
            }

            if (this.inteliGUISequence.Count <= 1 ||
                this.inteliGUILastClickTime.AddSeconds(2) >= DateTime.Now)
            {                
                this.inteliGUITimer.Stop();
                this.inteliGUITimer.Start();
            }
            else
            {
                this.inteliGUITimer.Stop();

                if (true == this.SimulateSequence())
                {
                    this.inteliGUISequence.Clear();
                    this.inteliGUILastClickTime = DateTime.Now;
                    return;
                }

                StringBuilder sb = new StringBuilder();

                foreach (int number in this.inteliGUISequence)
                {
                    sb.Append(string.Format(CultureInfo.InvariantCulture, "{0}, ", number));
                }

                sb.Remove(sb.Length - 2, 2);

                string sequence = sb.ToString();

                if (false == this.dataTable.Rows.Contains(sequence))
                {
                    this.dataTable.Rows.Add(0, sb.ToString());

                    this.Hopfield.AddAttractor(FactorySequenceAttractor());

                    int deep = 0;
                    int.TryParse(textBoxDeep.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out deep);

                    while (this.dataTable.Rows.Count > deep)
                    {
                        this.dataTable.Rows.RemoveAt(0);
                        this.Hopfield.DelAttractor(0);
                    }

                    for (int i = dataTable.Rows.Count - 1; i >= 0; i--)
                    {
                        dataTable.Rows[i][0] = i;
                    }

                    if (this.dataGridView.Rows.Count > 0)
                    {
                        this.dataGridView.Rows[dataTable.Rows.Count - 1].Selected = true;
                    }

                    if (this.ChangedAttractorList != null)
                    {
                        this.ChangedAttractorList(this, EventArgs.Empty);
                    }

                    this.SimulatorLearn();
                }

                this.inteliGUISequence.Clear();
            }

            this.inteliGUILastClickTime = DateTime.Now;
        }

        private void InitInteliGUI()
        {
            if (this.Hopfield.NCount != (this.pN1 * this.pN2))
            {
                this.Hopfield.Recreate(this.pN1 * this.pN2);
            }

            this.ReCreateInteliGUIButtons(this.pN1);
            this.textBoxD.Text = this.pN1.ToString(CultureInfo.InvariantCulture);
            this.Hopfield.Setd(this.pN1);
            this.dataTable.PrimaryKey = null;
            this.dataTable.Columns.Clear();

            this.dataTable.BeginInit();

            this.dataTable.Clear();
            this.dataTable.Columns.Clear();

            DataColumn dataColumnNumber = new DataColumn()
            {
                AllowDBNull = false,
                ColumnName = "Number",
                DataType = typeof(int),
                DefaultValue = 0
            };
            this.dataTable.Columns.Add(dataColumnNumber);
            DataColumn dataColumnSquence = new DataColumn()
            {
                AllowDBNull = false,
                ColumnName = "Sequence",
                DataType = typeof(string),
                DefaultValue = string.Empty,
            };
            this.dataTable.Columns.Add(dataColumnSquence);
            this.dataTable.PrimaryKey = new DataColumn[] { dataColumnSquence };

            this.dataTable.EndInit();

            if (this.ChangedAttractorList != null)
            {
                this.ChangedAttractorList(this, EventArgs.Empty);
            }
        }

        private void OpenHNFile()
        {
            this.Invoke(new MethodInvoker(() =>
            {
                this.info.Text = "Opening...";
            }));

            string fileName = string.Empty;

            this.Invoke(new MethodInvoker(() =>
            {
                using (OpenFileDialog ofd = new OpenFileDialog())
                {
                    ofd.Filter = "Hopfiled Sequence File (*.hsf)|*.hsf";
                    if (ofd.ShowDialog() != DialogResult.OK) return;

                    fileName = ofd.FileName;
                }
            }));

            if (true == string.IsNullOrEmpty(fileName)) return;

            DataSet ds = new DataSet("InteliGUI");
            ds.Locale = new CultureInfo("en-US");
            try
            {
                ds.Tables.Add(this.dataTable);
                this.dataTable.Rows.Clear();
                ds.ReadXml(fileName);
            }
            finally
            {
                ds.Tables.Remove(dataTable);
            }

            this.Invoke(new MethodInvoker(() =>
            {
                this.dataGridView.Refresh();
                this.info.Text = "Ready.";
            }));
        }

        private void ThreadOpenHNFile()
        {
            lock (Mutex)
            {
                this.OpenHNFile();
            }
        }

        public void FileOpen()
        {
            Thread th = new Thread(new ThreadStart(this.ThreadOpenHNFile));
            th.Start();
        }

        private void SaveHNFile()
        {
            this.Invoke(new MethodInvoker(() =>
            {
                this.info.Text = "Saving.";
            }));

            string fileName = string.Empty;

            this.Invoke(new MethodInvoker(() =>
            {
                using (SaveFileDialog sfd = new SaveFileDialog())
                {
                    sfd.Filter = "Hopfiled Sequence File (*.hsf)|*.hsf";
                    if (sfd.ShowDialog() != DialogResult.OK) return;

                    fileName = sfd.FileName;
                }
            }));

            if (true == string.IsNullOrEmpty(fileName)) return;

            DataSet ds = new DataSet("InteliGUI");
            ds.Locale = new CultureInfo("en-US");

            try
            {
                ds.Tables.Add(this.dataTable);
                ds.WriteXml(fileName);
            }
            finally
            {
                ds.Tables.Remove(this.dataTable);
            }

            this.Invoke(new MethodInvoker(() =>
            {
                this.info.Text = "Ready.";
            }));
        }

        private void ThreadSaveHNFile()
        {
            lock (Mutex)
            {
                this.SaveHNFile();
            }
        }

        public void FileSave()
        {
            Thread th = new Thread(new ThreadStart(this.ThreadSaveHNFile));
            th.Start();
        }

        private void LearningToMSEmin(string kind)
        {
            string k = kind.ToString();

            this.Hopfield.ClearW();

            while (this.Hopfield.LCount > 0)
            {
                this.Hopfield.DelAttractor(0);
            }

            foreach (List<int> list in GetSequences())
            {
                float[] newL = new float[this.Hopfield.NCount];
                int count = 0;
                int listCount = 0;
                foreach (int value in list)
                {
                    if ((listCount++) > this.pN1) break;

                    for (int i = 0; i < this.pN2; i++)
                    {
                        newL[count++] = this.Hopfield.GetFValue(value);
                    }
                }

                while (count < this.Hopfield.NCount)
                {
                    newL[count++] = this.Hopfield.GetFValue(0);
                }

                this.Hopfield.AddAttractor(newL);
            }

            this.Invoke(new MethodInvoker(() =>
            {
                this.info.Text = "Learning...";
            }));

            float mse = 1.0f;
            float mseL = mse;
            float mseLL = mseL;
            float mseLLL = mseLL;
            do
            {
                mseLLL = mseLL;
                mseLL = mseL;
                mseL = mse;
                mse = this.Hopfield.LearnStepHN(k);
                this.Invoke(new MethodInvoker(() =>
                {
                    this.infoMSE.Text = string.Format(CultureInfo.InvariantCulture, "MSE = {0}", mse.ToString(CultureInfo.InvariantCulture));
                }
                ));
            }
            while (mse != 0.0f && (mse < mseL || mseL < mseLL || mse < mseLL || mseLL < mseLLL || mseL < mseLLL));

            this.Invoke(new MethodInvoker(() =>
            {
                this.info.Text = "Ready.";
            }));
        }

        private void ThreadLearnMethod(object kind)
        {
            string k = kind.ToString();
            lock (Mutex)
            {
                this.LearningToMSEmin(k);
            }
        }

        public void SimulatorLearn()
        {
            ParameterizedThreadStart threadStart = new ParameterizedThreadStart(this.ThreadLearnMethod);
            Thread th = new Thread(threadStart);
            // 1st kind of learning - h - one step
            // 2nd kind of learning - d - recursive, the best
            // 3rd kind of learning - z - recursive, worst then above
            // 4th kind of learning - p - recursive, for predictor
            // 5th kind of learning - s - recursive, for sequences
            th.Start("s");
        }

        private void SimulateToMSEmin(object kind)
        {
            string k = kind.ToString();
            this.Invoke(new MethodInvoker(() =>
            {
                info.Text = "Simulating...";
            }));
            float mse = 1.0f;
            float mseMin = mse;
            float mseMinL = mseMin;
            do
            {
                mseMinL = mseMin;
                mseMin = mse;
                mse = this.Hopfield.CompHN(k);
                this.Invoke(new MethodInvoker(() =>
                {
                    infoMSE.Text = string.Format(CultureInfo.InvariantCulture, "MSE = {0}", mse.ToString(CultureInfo.InvariantCulture));
                }
                ));
            }
            while (mse != 0.0f && (mse < mseMin || mseMin < mseMinL || mse < mseMinL));
            this.Invoke(new MethodInvoker(() =>
            {
                info.Text = "Ready.";
            }));
        }

        private void ThreadSimulateMethod(object kind)
        {
            string k = kind.ToString();
            lock (Mutex)
            {
                this.SimulateToMSEmin(k);
            }
        }

        private bool SimulateSequence()
        {
            this.Hopfield.SetY(FactorySequenceAttractor());

            this.SimulateToMSEmin("s");

            int sequenceCount = (this.inteliGUISequence.Count - 1) * this.pN2;
            float mse = 0.0f;
            int recognizedAttractor = this.Hopfield.GetRecognizedLIndex(sequenceCount, out mse);

            if (recognizedAttractor < 0) return false;

            if (MessageBox.Show("Recognized sequence with MSE = " + mse.ToString(CultureInfo.InvariantCulture) + " do you want to see it?", "Question", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
            {
                this.dataGridView.Rows[recognizedAttractor].Selected = true;
                this.AnimateSequence();
                return true;
            }

            return false;
        }

        [HopfieldSimulatorHide]
        public void SimulatorSimulate()
        {
        }

        [HopfieldSimulatorHide]
        public void AttractorAdd()
        {
        }

        private void AttractorRemoveFromGrid()
        {
            this.Invoke(new MethodInvoker(() =>
            {
                foreach (var selectedRow in this.dataGridView.SelectedRows)
                {
                    this.dataGridView.Rows.Remove(selectedRow as DataGridViewRow);
                }

                if (this.ChangedAttractorList != null)
                {
                    this.ChangedAttractorList(this, EventArgs.Empty);
                }
            }));
        }

        private void ThreadRemoveAttractorMethod()
        {
            lock (Mutex)
            {
                this.AttractorRemoveFromGrid();
            }
        }

        public void AttractorRemoveSelected()
        {
            ThreadStart threadStart = new ThreadStart(this.ThreadRemoveAttractorMethod);
            Thread th = new Thread(threadStart);
            th.Start();
        }

        private List<List<int>> GetSequences()
        {
            List<List<int>> returnValue = new List<List<int>>();

            foreach (DataRow row in dataTable.Rows)
            {
                List<int> list = new List<int>();
                string value = row[1] as string;
                if (value == null) continue;

                value = value.Replace(" ", string.Empty);
                foreach (string strNumber in value.Split(','))
                {
                    int number = 0;
                    if (true == int.TryParse(strNumber, NumberStyles.Integer, CultureInfo.InvariantCulture, out number))
                    {
                        list.Add(number);
                    }
                }

                returnValue.Add(list);
            }

            return returnValue;
        }

        private void AnimateSequence()
        {
            if (this.dataGridView.SelectedRows.Count != 1) return;

            string value = this.dataGridView.SelectedRows[0].Cells[1].Value as string;
            value = value.Replace(" ", string.Empty);
            foreach (string strNumber in value.Split(','))
            {
                int number = 0;
                int.TryParse(strNumber, NumberStyles.Integer, CultureInfo.InvariantCulture, out number);
                Color saveColor = this.panelInteliGUI.Controls[number].ForeColor;
                try
                {
                    this.panelInteliGUI.Controls[number].ForeColor = Color.Red;
                    this.panelInteliGUI.Controls[number].Refresh();
                    Thread.Sleep(250);
                }
                finally
                {
                    this.panelInteliGUI.Controls[number].ForeColor = saveColor;
                    this.panelInteliGUI.Controls[number].Refresh();
                }
            }
        }

        private void dataGridView_Click(object sender, EventArgs e)
        {
            this.AnimateSequence();
        }

        private void dataGridView_RowsRemoved(object sender, DataGridViewRowsRemovedEventArgs e)
        {
            if (this.Hopfield.LCount > e.RowIndex)
            {
                this.Hopfield.DelAttractor(e.RowIndex);
            }

            for (int i = dataGridView.Rows.Count - 1; i >= 0; i--)
            {
                dataGridView.Rows[i].Cells[0].Value = i;
            }

            if (this.ChangedAttractorList != null)
            {
                this.ChangedAttractorList(this, EventArgs.Empty);
            }
        }

        private void textBoxD_TextChanged(object sender, EventArgs e)
        {
            int d = 0;
            if (true == int.TryParse(textBoxD.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out d))
            {
                this.Hopfield.Setd(d);

                if (this.ChangedD != null)
                {
                    ChangedD(this, EventArgs.Empty);
                }
            }
        }

        private void textBoxEta_TextChanged(object sender, EventArgs e)
        {
            float eta = 0.7f;
            if (true == float.TryParse(textBoxEta.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out eta))
            {
                this.Hopfield.Seteta(eta);

                if (this.ChangedEta != null)
                {
                    ChangedEta(this, EventArgs.Empty);
                }
            }
        }

        private void textBoxN1_TextChanged(object sender, EventArgs e)
        {
            int n = 0;
            if (true == int.TryParse(textBoxN1.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out n))
            {
                this.pN1 = n;
                this.InitInteliGUI();
            }
        }

        private void textBoxN2_TextChanged(object sender, EventArgs e)
        {
            int n = 0;
            if (true == int.TryParse(textBoxN2.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out n))
            {
                this.pN2 = n;
                this.InitInteliGUI();
            }
        }

        private void textBoxDeep_TextChanged(object sender, EventArgs e)
        {
            int deep = 0;
            if (true == int.TryParse(textBoxDeep.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out deep))
            {
                this.pDeep = deep;
            }
        }

        #endregion

        #region Events

        public event EventHandler ChangedAttractorList;
        public event EventHandler ChangedD;
        public event EventHandler ChangedEta;

        #endregion
    }
}
